use config::{Map, Value};
use rstest::rstest;

use super::super::*;
use crate::tests::common::{assert_expected_result_or_error, enforce_types_are_equal};
use crate::tests::fixtures::{
    RANDOM_STRINGS,
    TEST_VALUE_KIND_ARRAY,
    TEST_VALUE_KIND_VECTOR,
    TEST_VALUE_VECTOR,
};

#[test]
fn test_unpackable() {
    struct MockValueKind;

    // Implementing Unpackable for MockValueKind forces Unpackable to exist and
    // exhibit the desired interface.
    impl Unpackable for MockValueKind {
        fn unpack_numeric_to_f64(&self) -> Result<f64, Error> {
            Ok(0.)
        }

        fn unpack_string(&self) -> Result<String, Error> {
            Ok("".to_string())
        }

        fn unpack_string_or_nil(&self) -> Result<Option<String>, Error> {
            Ok(Some("".to_string()))
        }

        fn unpack_table(&self) -> Result<BTreeMap<String, &ValueKind>, Error> {
            Ok(BTreeMap::new())
        }

        fn unpack_array(&self) -> Result<Vec<&ValueKind>, Error> {
            Ok(vec![])
        }

        fn explain_unexpected_kind<T>(&self, expected: &str) -> Result<T, Error> {
            Err(Error::ValueError(expected.to_string()))
        }
    }

    let _ = MockValueKind;
}

#[test]
fn test_tile_value_kind_is_value_kind() {
    enforce_types_are_equal(ValueKind::default(), TileValueKind::default());
}

#[test]
fn test_tile_value_kind_implements_unpackable() {
    fn enforce_type_implements_unpackable<T: Unpackable>(_: T) {}

    enforce_type_implements_unpackable(TileValueKind::default());
}

#[rstest]
#[case::valid_case_float(ValueKind::Float(-342.43), Some(-342.43), None)]
#[case::valid_case_float_zero(ValueKind::Float(0.), Some(0.), None)]
#[case::valid_case_i64(ValueKind::I64(-9272), Some(-9272.), None)]
#[case::valid_case_i128(ValueKind::I128(54272), Some(54272.), None)]
#[case::valid_case_u64(ValueKind::U64(32409), Some(32409.), None)]
#[case::valid_case_u64_zero(ValueKind::U64(0), Some(0.), None)]
#[case::valid_case_u128(ValueKind::U128(49280345), Some(49280345.), None)]
#[case::invalid_case_table(
    ValueKind::Table({
        let mut map = Map::new();
        map.insert(
            RANDOM_STRINGS[2].clone(),
            Value::new(
                None,
                TEST_VALUE_KIND_ARRAY.clone(),
            ),
        );
        map
    }),
    None,
    Some(Error::ValueError(format!(
    "expected a numeric (Float, I64, I128, U64, U128), got\n{:#?}",
        ValueKind::Table({
            let mut map = Map::new();
            map.insert(
                RANDOM_STRINGS[2].clone(),
                Value::new(
                    None,
                    TEST_VALUE_KIND_ARRAY.clone(),
                ),
            );
            map
        })
    )))
)]
#[case::invalid_case_nil(
    ValueKind::Nil,
    None,
    Some(Error::ValueError("expected a numeric (Float, I64, I128, U64, U128), got Nil".to_string()))
)]
fn test_tile_value_kind_unpack_numeric_to_f64(
    #[case] value_kind: ValueKind,
    #[case] expected_float: Option<f64>,
    #[case] expected_error: Option<Error>,
) {
    let result = value_kind.unpack_numeric_to_f64();
    assert_expected_result_or_error(result, expected_float, expected_error);
}

#[rstest]
#[case::valid_case_string(
    ValueKind::String(RANDOM_STRINGS[4].clone()),
    Some(RANDOM_STRINGS[4].clone()),
    None
)]
#[case::valid_case_string_empty(
    ValueKind::String(RANDOM_STRINGS[0].clone()),
    Some(RANDOM_STRINGS[0].clone()),
    None
)]
#[case::invalid_case_u64(
    ValueKind::U64(32409),
    None,
    Some(Error::ValueError("expected a String, got U64(32409)".to_string()))
)]
#[case::invalid_case_table(
    ValueKind::Table(Map::new()),
    None,
    Some(Error::ValueError("expected a String, got Table({})".to_string()))
)]
#[case::invalid_case_nil(
    ValueKind::Nil,
    None,
    Some(Error::ValueError("expected a String, got Nil".to_string())))
]
fn test_tile_value_kind_unpack_string(
    #[case] value_kind: ValueKind,
    #[case] expected_string: Option<String>,
    #[case] expected_error: Option<Error>,
) {
    let result = value_kind.unpack_string();
    assert_expected_result_or_error(result, expected_string, expected_error);
}

#[rstest]
#[case::valid_case_string(
    ValueKind::String(RANDOM_STRINGS[4].clone()),
    Some(Some(RANDOM_STRINGS[4].clone())),
    None,
)]
#[case::valid_case_string_empty(
    ValueKind::String(RANDOM_STRINGS[0].clone()),
    Some(Some(RANDOM_STRINGS[0].clone())),
    None,
)]
#[case::valid_case_nil(ValueKind::Nil, Some(None), None)]
#[case::invalid_case_u64(
    ValueKind::U64(32409),
    None,
    Some(Error::ValueError("expected a String, got U64(32409)".to_string())),
)]
#[case::invalid_case_table(
    ValueKind::Table(Map::new()),
    None,
    Some(Error::ValueError("expected a String, got Table({})".to_string())),
)]
fn test_tile_value_kind_unpack_string_or_nil(
    #[case] value_kind: ValueKind,
    #[case] expected_string: Option<Option<String>>,
    #[case] expected_error: Option<Error>,
) {
    let result = value_kind.unpack_string_or_nil();
    assert_expected_result_or_error(result, expected_string, expected_error);
}

#[rstest]
#[case::valid_case_table_with_content(
    ValueKind::Table({
        let mut map = Map::new();
        map.insert(
            RANDOM_STRINGS[0].clone(),
            Value::new(
                Some(&"file by user".to_string()),
                ValueKind::Float(9233.452),
            ),
        );
        map.insert(
            RANDOM_STRINGS[1].clone(),
            Value::new(
                None,
                TEST_VALUE_KIND_ARRAY.clone(),
            ),
        );
        map
    }),
    Some({
        let mut map = BTreeMap::new();
        map.insert(
            RANDOM_STRINGS[0].clone(),
            &ValueKind::Float(9233.452),
        );
        map.insert(
            RANDOM_STRINGS[1].clone(),
            &TEST_VALUE_KIND_ARRAY,
        );
        map
    }),
    None
)]
#[case::valid_case_table_empty(ValueKind::Table(Map::new()), Some(BTreeMap::new()), None)]
#[case::invalid_case_array(
    ValueKind::Array(vec![]),
    None,
    Some(Error::ValueError("expected a Table (Map), got Array([])".to_string()))
)]
#[case::invalid_case_string(
    ValueKind::String("vec![ 1, 2, 3, 4, 5 ]".to_string()),
    None,
    Some(Error::ValueError(
        "expected a Table (Map), got String(\"vec![ 1, 2, 3, 4, 5 ]\")".to_string()
    )),
)]
#[case::invalid_case_string_multiline(
    ValueKind::String("vec![ 1, 2, 3, 4, 5, ]".to_string()),
    None,
    Some(Error::ValueError(
        "expected a Table (Map), got\nString(\n    \"vec![ 1, 2, 3, 4, 5, ]\",\n)".to_string()
    )),
)]
#[case::invalid_case_u64(
    ValueKind::U64(32409),
    None,
    Some(Error::ValueError("expected a Table (Map), got U64(32409)".to_string()))
)]
#[case::invalid_case_nil(
    ValueKind::Nil,
    None,
    Some(Error::ValueError("expected a Table (Map), got Nil".to_string()))
)]
fn test_tile_value_kind_unpack_table(
    #[case] value_kind: ValueKind,
    #[case] expected_table: Option<BTreeMap<String, &ValueKind>>,
    #[case] expected_error: Option<Error>,
) {
    let result = value_kind.unpack_table();

    assert_expected_result_or_error(result, expected_table, expected_error);
}

#[rstest]
#[case::valid_case_array(
    ValueKind::Array(vec![
        TEST_VALUE_VECTOR[3].clone(),
        TEST_VALUE_VECTOR[0].clone(),
        TEST_VALUE_VECTOR[1].clone(),
        TEST_VALUE_VECTOR[0].clone()
    ]),
    Some(vec![
        &TEST_VALUE_KIND_VECTOR[3],
        &TEST_VALUE_KIND_VECTOR[0],
        &TEST_VALUE_KIND_VECTOR[1],
        &TEST_VALUE_KIND_VECTOR[0]
    ]),
    None
)]
#[case::valid_case_array_empty(
    ValueKind::Array(vec![]),
    Some(vec![]),
    None
)]
#[case::invalid_case_table(
    ValueKind::Table(Map::new()),
    None,
    Some(Error::ValueError("expected an Array, got Table({})".to_string()))
)]
#[case::invalid_case_u32(
    ValueKind::U64(32409),
    None,
    Some(Error::ValueError("expected an Array, got U64(32409)".to_string()))
)]
#[case::invalid_case_nil(
    ValueKind::Nil,
    None,
    Some(Error::ValueError("expected an Array, got Nil".to_string()))
)]
fn test_tile_value_kind_unpack_array(
    #[case] value_kind: ValueKind,
    #[case] expected_array: Option<Vec<&ValueKind>>,
    #[case] expected_error: Option<Error>,
) {
    let result = value_kind.unpack_array();
    assert_expected_result_or_error(result, expected_array, expected_error);
}
